#include "board.h"

#include <QDebug>

#include "hole.h"
#include "ball.h"

Board::Board(qint32 size, QObject *parent)
    : QGraphicsScene(parent)
    , m_size(size)
{
    QList<BoardItem::pos_t> positions(game_positions());
    for (QList<BoardItem::pos_t>::const_iterator i = positions.begin(); i != positions.end(); ++i)
    {
        m_holes.push_back( new Hole(this, 1, m_size, *i) );
        m_scene.addItem(m_holes.back());
        addItem(m_holes.back());
    }

    reset();
}

void Board::drag_ball(Ball *ball)
{
    if (ball) highlight_holes(ball->game_position(), true);
}

void Board::drop_ball(Ball *ball)
{
    if (ball)
    {
        highlight_holes(ball->game_position(), false);
        BoardItem::pos_t target = nearest_pos(ball->current_center());
        if (accept_move(ball->game_position(), target))
        {
            ball->set_game_position(target);
            if (no_moves()) emit finish();
        }
        else
        {
            ball->set_game_position(ball->game_position());
        }
    }
}

QList<BoardItem::pos_t> Board::game_positions() const
{
    static qint32 position[][2] =
    {
                        {0, 2}, {0, 3}, {0, 4},
                        {1, 2}, {1, 3}, {1, 4},
        {2, 0}, {2, 1}, {2, 2}, {2, 3}, {2, 4}, {2, 5}, {2, 6},
        {3, 0}, {3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {3, 6},
        {4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 5}, {4, 6},
                        {5, 2}, {5, 3}, {5, 4},
                        {6, 2}, {6, 3}, {6, 4},
    };

    QList<BoardItem::pos_t> positions;
    for (qint32 i = 0; i != sizeof(position)/sizeof(qint32[2]); ++i)
        positions.push_back(BoardItem::pos_t(position[i][0], position[i][1]));

    return positions;
}

QList<BoardItem::pos_t> Board::valid_moves(const BoardItem::pos_t &src) const
{
    QList<BoardItem::pos_t> neighbours;
    QList<BoardItem::pos_t> board_positions(game_positions());

    BoardItem::pos_t upper(src.first, src.second + 1);
    BoardItem::pos_t over_upper(upper.first, upper.second + 1);
    if (board_positions.contains(upper) && board_positions.contains(over_upper))
        if ( busy(upper) && !busy(over_upper) ) neighbours.push_back(over_upper);

    BoardItem::pos_t lower(src.first, src.second - 1);
    BoardItem::pos_t under_lower(lower.first, lower.second - 1);
    if (board_positions.contains(lower) && board_positions.contains(under_lower))
        if ( busy(lower) && !busy(under_lower) ) neighbours.push_back(under_lower);

    BoardItem::pos_t left(src.first - 1, src.second);
    BoardItem::pos_t left_left(left.first - 1, left.second);
    if (board_positions.contains(left) && board_positions.contains(left_left))
        if ( busy(left) && !busy(left_left) ) neighbours.push_back(left_left);

    BoardItem::pos_t right(src.first + 1, src.second);
    BoardItem::pos_t right_right(right.first + 1, right.second);
    if (board_positions.contains(right) && board_positions.contains(right_right))
        if ( busy(right) && !busy(right_right) ) neighbours.push_back(right_right);

    return neighbours;
}

bool Board::busy(const BoardItem::pos_t &src) const
{
    for (QList<Ball *>::const_iterator i = m_balls.begin(); i != m_balls.end(); ++i)
        if ( (*i)->game_position() == src ) return true;
    return false;
}

bool Board::accept_move(const BoardItem::pos_t &src, const BoardItem::pos_t &dst)
{
    if (valid_moves(src).contains(dst))
    {
        BoardItem::pos_t med( (src.first + dst.first) / 2, (src.second + dst.second) / 2);
        for (QList<Ball *>::iterator i = m_balls.begin(); i != m_balls.end(); ++i)
        {
            if ( (*i)->game_position() == med )
            {
                Ball *ball = *i;
                m_balls.erase(i);
                delete ball;
                emit eaten();
                return true;
            }
        }
    }

    return false;
}

BoardItem::pos_t Board::nearest_pos(const QPointF &p) const
{
    Hole *nearest_hole = 0;
    qreal min_dist = itemsBoundingRect().width() * itemsBoundingRect().width();
    for (QList<Hole *>::const_iterator i = m_holes.begin(); i != m_holes.end(); ++i)
    {
        #define SQR(x) ((x) * (x))
        qreal dist = SQR((*i)->scene_item_center().x() - p.x())
                + SQR((*i)->scene_item_center().y() - p.y());
        #undef SQR
        if (dist < min_dist)
        {
            min_dist = dist;
            nearest_hole = *i;
        }
    }
    return nearest_hole->game_position();
}

void Board::highlight_holes(const BoardItem::pos_t &pos, bool enable) const
{
    QList<BoardItem::pos_t> neighbours(valid_moves(pos));
    for (QList<Hole *>::const_iterator i = m_holes.begin(); i != m_holes.end(); ++i)
        if (neighbours.contains((*i)->game_position())) (*i)->setHighlight(enable);
}

bool Board::no_moves() const
{
    for (QList<Ball *>::const_iterator i = m_balls.begin(); i != m_balls.end(); ++i)
        if (!valid_moves( (*i)->game_position() ).empty()) return false;

    return true;
}

void Board::reset()
{
    for (QList<Ball *>::const_iterator i = m_balls.begin(); i != m_balls.end(); ++i) delete (*i);
    m_balls.clear();

    QList<BoardItem::pos_t> positions(game_positions());
    for (QList<BoardItem::pos_t>::const_iterator i = positions.begin(); i != positions.end(); ++i)
    {
        if ( (i->first != 3) || (i->second != 3) )
        {
            m_balls.push_back( new Ball(this, 2, m_size, *i) );
            m_scene.addItem(m_balls.back());
            addItem(m_balls.back());
        }
    }
}
